package com.ant.androidBuddy;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

public class Interpreter{

	static AnimationActivity animationActivity;

	/**
	 * A parsing exception
	 */
	@SuppressWarnings("serial")
	private class ParseException extends Exception{

		public int lineNo=-1;
		/**
		 * constructor
		 * @param message detailed error message
		 */
		public ParseException(String message){
			super(message);
		}
		public ParseException(String message, int line){
			super(message);
			this.lineNo=line;
		}
	}

	/**
	 * the line number of the problem - should never be -1
	 */
	public static int lineNo=-1;

	/**
	 * The hashmap mapping the name to the Android
	 */
	private static HashMap<String, AndroidCharacter> androidList;

	/**
	 * list of problems encountered while parsing
	 */
	public static ArrayList<ParseException> problems;

	//the reader
	static BufferedReader reader;

	//if code is running, it won't run again
	static boolean running=false;

	//starts the animation
	static void start(BufferedReader read, AnimationActivity animationActivityParameter){
		//do nothing if it is already running
		if(running){
			return;
		}
		//it is now running
		running=true;
		//reset the layout
		AndroidCharacter.layout.removeAllViewsInLayout();
		//destroy the drawing cache of every android, preventing memory leaks
		if(problems!=null){
			for (AndroidCharacter android:androidList.values()){
				if(android!=null){
					android.destroyDrawingCache();
				}
			}
		}
		//reset the colours
		AndroidCharacter.colourListIndex=0;
		//set stuff
		animationActivity=animationActivityParameter;
		reader=read;
		//reset stuff
		androidList=new HashMap<String, AndroidCharacter>();
		problems=new ArrayList<ParseException>();
		lineNo=0;
		//read the first line
		readLine();
	}

	//stop running
	static void stop(){
		//get any problems
		String probs=getProblems();
		//is no longer running
		running=false;
		//if there are problems
		if(probs.length()!=0){
			//show them
			animationActivity.showAlert(getProblems());
		}
		//if there are no problems
		else{
			animationActivity.nextTutorial();
		}
	}

	//reads a line
	static void readLine(){
		try {
			//if end of string
			if(!reader.ready()){
				stop();
				return;
			}
		} catch (IOException e) {
			//stop if there is an IO problem
			problems.add(new Interpreter().new ParseException("IOException reading line: "+e.getMessage(),lineNo));
			stop();
			return;
		}
		try{
			//next line
			lineNo++;
			//get the line
			String line=reader.readLine();
			//convert to lowercase
			line=line.trim().toLowerCase();
			//parse the line
			parseLine(line);
		}
		catch (ParseException e){
			//add to the list of problems
			e.lineNo=lineNo;
			problems.add(e);
			readLine();
		}
		//this shouldn't happen
		catch(Exception e){
			//add to the list of problems
			problems.add(new Interpreter().new ParseException("unknown problem: "+e.getMessage(),lineNo));
			readLine();
		}
	}

	//parse a line
	private static void parseLine(String line) throws ParseException {
		//ignore empty lines
		if(line.length()==0){
			return;
		}
		//initializations start with "android"
		if(line.startsWith("android ")){
			//remove "android " from the start
			line=line.substring("android ".length()).trim();
			//check if it contains '='
			if(!line.contains("=")){
				throw new Interpreter().new ParseException("new androids must have '='");
			}
			//get the index of '='
			int indexOfEquals=line.indexOf('=');
			//get the name
			String name=line.substring(0, indexOfEquals).trim();
			//try to get the android with that name
			AndroidCharacter existingAndroid=androidList.get(name);
			//if there is already an android with the name
			if(existingAndroid!=null){
				throw new Interpreter().new ParseException(name+" already exists");
			}
			//if the name contains a space
			else if(name.contains(" ")){
				throw new Interpreter().new ParseException(name+" should not contain spaces");
			}
		}
		//if the line contains a dot, it might be a method/field
		if(line.contains(".")){
			//the position of the first dot
			int indexOfDot=line.indexOf('.');
			//the name is everything before the dot
			String name=line.substring(0,indexOfDot).trim();
			//try to get the android
			AndroidCharacter android=androidList.get(name);
			//if the android exists
			if(android!=null){
				line=line.substring(indexOfDot+1);
				doAction(line, android, name);
			}
			else{
				throw new Interpreter().new ParseException("couldn't understand line");
			}
		}
		//if the android is being set
		else if(line.contains("=")){
			//the position of the '='
			int indexOfEquals=line.indexOf('=');
			//the name of the android is before the '='
			String name=line.substring(0,indexOfEquals).trim();
			line=line.substring(indexOfEquals+1).trim();
			//set the android
			doSet(line,name);
		}
		//the line is unreadable
		else{
			throw new Interpreter().new ParseException("couldn't understand line");
		}
	}

	//get a list of any problems encountered while parsing
	static String getProblems(){
		//initialise the string
		String problemsString="";
		//go through every problem
		for(Iterator<ParseException> problemsIterator=problems.iterator();problemsIterator.hasNext();){
			//get the exception
			ParseException e=problemsIterator.next();
			//add it to the string
			problemsString+="line "+e.lineNo+":\t"+e.getMessage()+"\n";
		}
		//remove last new line
		try{
			problemsString=problemsString.substring(0, problemsString.length()-1);
		}
		catch(Exception e){}
		//return it
		return problemsString;
	}

	/**
	 * sets an android pointer
	 * @param line what to set it to
	 * @param name the pointer name
	 */
	private static void doSet(String line, String name) throws ParseException{
		//constructor
		if(line.contentEquals("new android()")||line.contentEquals("new android ()")){
			androidList.put(name, new AndroidCharacter());
			Interpreter.readLine();
		}
		//null
		else if(line.contentEquals("null")){

			AndroidCharacter existingAndroid=androidList.get(name);
			androidList.put(name, null);
			if(existingAndroid==null||!androidList.containsValue(existingAndroid)){
				existingAndroid.hide();
			}
			else{
				Interpreter.readLine();
			}

			/*
			AndroidCharacter existingAndroid=androidList.get(name);
			if(existingAndroid!=null&&!(existingAndroid instanceof NullAndroid)){
				androidList.put(name, new Interpreter().new NullAndroid());
				if(!androidList.containsValue(existingAndroid)){
					existingAndroid.hide();
					return;
				}
				Interpreter.readLine();
				return;
			}
			androidList.put(name, new Interpreter().new NullAndroid());*/
		}
		//other android
		else{
			AndroidCharacter android = androidList.get(line);
			//if the other android does not exist
			if(android==null){
				throw new Interpreter().new ParseException(line+" does not exist");
			}
			androidList.put(name, android);
		Interpreter.readLine();
		}
	}

	/**
	 * Does an action on an android
	 * @param line the line to parse
	 * @param android the android to perform the action on
	 * @param name the name of the android
	 * @throws ParseException if there is a problem while parsing
	 */
	private static void doAction(String line, AndroidCharacter android,String name) throws ParseException{
		//if the android should be null
		if(android==null){
			throw new Interpreter().new ParseException("can't operate on a null object");
		}
		//jumpUp()
		if(line.startsWith("jumpup")){
			//remove "jump"
			line=line.substring("jumpup".length()).trim();
			//only accept if no parameters
			if(line.contentEquals("()")||line.contentEquals("( )")){
				//jump
				android.jump("up", 100);
			}
			//if invalid parameters
			else if(line.contains("(")||line.contains(")")){
				throw new Interpreter().new ParseException("This method should have nothing inside the brackets");
			}
			else{
				throw new Interpreter().new ParseException("Methods must include brackets");
			}
		}
		//jumpDown()
		else if(line.startsWith("jumpdown")){
			//remove "jump"
			line=line.substring("jumpdown".length()).trim();
			//only accept if no parameters
			if(line.contentEquals("()")||line.contentEquals("( )")){
				//jump
				android.jump("down", 100);
			}
			//if invalid parameters
			else if(line.contains("(")||line.contains(")")){
				throw new Interpreter().new ParseException("This method should have nothing inside the brackets");
			}
			else{
				throw new Interpreter().new ParseException("Methods must include brackets");
			}
		}
		//show()
		else if(line.startsWith("show")){
			//remove "show"
			line=line.substring("show".length()).trim();
			if(!(
					line.contentEquals("()")||
					line.contentEquals("( )")
					)){
				throw new Interpreter().new ParseException("This method should have nothing inside the brackets");
			}
			android.show();
		}

		//show()
		else if(line.startsWith("hide")){
			//remove "show"
			line=line.substring("hide".length()).trim();
			if(!(
					line.contentEquals("()")||
					line.contentEquals("( )")
					)){
				throw new Interpreter().new ParseException("This method should have nothing inside the brackets");
			}
			android.hide();
		}

		//colour=String
		else if (line.startsWith("colour")){
			//remove "colour"
			line=line.substring("colour".length()).trim();
			//if the line does not start with '='
			if(!line.startsWith("=")){
				throw new Interpreter().new ParseException("There must be a '=' sign after \"colour");
			}
			//get the colour
			String colour=line.substring(1).trim();
			//if not a valid colour
			if(!(
					colour.contentEquals("blue")||
					colour.contentEquals("green")||
					colour.contentEquals("purple")||
					colour.contentEquals("red")||
					colour.contentEquals("orange")
					)){
				throw new Interpreter().new ParseException(colour+" is not a valid colour");
			}
			android.setColour(colour);
		}
		//move(String direction, int distance)
		else if (line.startsWith("move")){
			//remove "move"
			line=line.substring("move".length()).trim();
			//if there is no '('
			if(!line.startsWith("(")){
				throw new Interpreter().new ParseException("methods must include brackets");
			}
			//remove '('
			line=line.substring(1).trim();
			//if there is no comma
			if(!line.contains(",")){
				throw new Interpreter().new ParseException("this method must have two parameters separated by a comma");
			}
			//get the position of the first ','
			int indexOfComma=line.indexOf(',');
			//get the direction
			String direction=line.substring(0,indexOfComma).trim();
			//if the direction is invalid
			if(!(
					direction.equals("l")||
					direction.equals("r")
					)){
				throw new Interpreter().new ParseException("the direction must be 'l' or 'r'");
			}
			//remove the direction from the line
			line=line.substring(indexOfComma+1).trim();
			//if there is no closing bracket
			if(!line.endsWith(")")){
				throw new Interpreter().new ParseException("methods should include brackets");
			}
			//get the distance as a string
			String distanceString = line.substring(0,line.length()-1).trim();
			//convert it to an integer
			int distance;
			try{
				distance=Integer.parseInt(distanceString);
			}
			catch(NumberFormatException e){
				throw new Interpreter().new ParseException(distanceString+" should be a whole number");
			}
			//move
			android.move(direction, distance);
		}
		//wave(string arm)
		else if (line.startsWith("spin")){
			//remove "wave"
			line=line.substring("spin".length()).trim();
			//if there are no brackets

			if(!(
					line.contentEquals("()")||
					line.contentEquals("( )")
					)){
				throw new Interpreter().new ParseException("This method should have nothing inside the brackets");
			}
			//spin
			android.spin();
		}
		else{
			throw new Interpreter().new ParseException(line+ " is not a valid method for the android");
		}
	}

}
